// Copyright Contributors to the Open Shading Language project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage

#include <vector>
#include <string>
#include <cstdio>
#include <algorithm>

#include <OpenImageIO/strutil.h>

#include "oslexec_pvt.h"


OSL_NAMESPACE_ENTER


namespace pvt {   // OSL::pvt

using OIIO::spin_lock;
using OIIO::ParamValue;
using OIIO::ParamValueList;

static int next_id = 0; // We can statically init an int, not an atomic



ShaderInstance::ShaderInstance (ShaderMaster::ref master,
                                string_view layername)
    : m_master(master),
      //DON'T COPY  m_instsymbols(m_master->m_symbols),
      //DON'T COPY  m_instops(m_master->m_ops), m_instargs(m_master->m_args),
      m_layername(layername),
      m_writes_globals(false),
      m_outgoing_connections(false),
      m_renderer_outputs(false), m_has_error_op(false), m_merged_unused(false),
      m_last_layer(false), m_entry_layer(false),
      m_firstparam(m_master->m_firstparam), m_lastparam(m_master->m_lastparam),
      m_maincodebegin(m_master->m_maincodebegin),
      m_maincodeend(m_master->m_maincodeend)
{
    m_id = ++(*(atomic_int *)&next_id);
    shadingsys().m_stat_instances += 1;

    // We don't copy the symbol table yet, it stays with the master, but
    // we'll keep track of local override information in m_instoverrides.

    // Make it easy for quick lookups of common symbols
    m_Psym = findsymbol (Strings::P);
    m_Nsym = findsymbol (Strings::N);

    // Adjust statistics
    ShadingSystemImpl &ss (shadingsys());
    off_t parammem = vectorbytes (m_iparams)
        + vectorbytes (m_fparams) + vectorbytes (m_sparams);
    off_t totalmem = (parammem + sizeof(ShaderInstance));
    {
        spin_lock lock (ss.m_stat_mutex);
        ss.m_stat_mem_inst_paramvals += parammem;
        ss.m_stat_mem_inst += totalmem;
        ss.m_stat_memory += totalmem;
    }
}



ShaderInstance::~ShaderInstance ()
{
    shadingsys().m_stat_instances -= 1;

    OSL_DASSERT (m_instops.size() == 0 && m_instargs.size() == 0);
    ShadingSystemImpl &ss (shadingsys());
    off_t symmem = vectorbytes (m_instsymbols) + vectorbytes(m_instoverrides);
    off_t parammem = vectorbytes (m_iparams)
        + vectorbytes (m_fparams) + vectorbytes (m_sparams);
    off_t connectionmem = vectorbytes (m_connections);
    off_t totalmem = (symmem + parammem + connectionmem +
                       sizeof(ShaderInstance));
    {
        spin_lock lock (ss.m_stat_mutex);
        ss.m_stat_mem_inst_syms -= symmem;
        ss.m_stat_mem_inst_paramvals -= parammem;
        ss.m_stat_mem_inst_connections -= connectionmem;
        ss.m_stat_mem_inst -= totalmem;
        ss.m_stat_memory -= totalmem;
    }
}



int
ShaderInstance::findsymbol (ustring name) const
{
    for (size_t i = 0, e = m_instsymbols.size();  i < e;  ++i)
        if (m_instsymbols[i].name() == name)
            return (int)i;

    // If we haven't yet copied the syms from the master, get it from there
    if (m_instsymbols.empty())
        return m_master->findsymbol (name);

    return -1;
}



int
ShaderInstance::findparam (ustring name) const
{
    if (m_instsymbols.size())
        for (int i = m_firstparam, e = m_lastparam;  i < e;  ++i)
            if (m_instsymbols[i].name() == name)
                return i;

    // Not found? Try the master.
    for (int i = m_firstparam, e = m_lastparam;  i < e;  ++i)
        if (master()->symbol(i)->name() == name)
            return i;

    return -1;
}



void *
ShaderInstance::param_storage (int index)
{
    const Symbol *sym = m_instsymbols.size() ? symbol(index) : mastersymbol(index);

    // Get the data offset. If there are instance overrides for symbols,
    // check whether we are overriding the array size, otherwise just read
    // the offset from the symbol.  Overrides for arraylength -- which occur
    // when an indefinite-sized array parameter gets a value (with a concrete
    // length) -- are special, because in that case the new storage is
    // allocated at the end of the previous parameter list, and thus is not
    // where the master may have thought it was.
    int offset;
    if (m_instoverrides.size() && m_instoverrides[index].arraylen())
        offset = m_instoverrides[index].dataoffset();
    else
        offset = sym->dataoffset();

    TypeDesc t = sym->typespec().simpletype();
    if (t.basetype == TypeDesc::INT) {
        return &m_iparams[offset];
    } else if (t.basetype == TypeDesc::FLOAT) {
        return &m_fparams[offset];
    } else if (t.basetype == TypeDesc::STRING) {
        return &m_sparams[offset];
    } else {
        return NULL;
    }
}



const void *
ShaderInstance::param_storage (int index) const
{
    // Rather than repeating code here, just use const_cast and call the
    // non-const version of this method.
    return (const_cast<ShaderInstance*>(this))->param_storage(index);
}



// Can a parameter with type 'a' be bound to a value of type b?
// Requires matching types (and if arrays, matching lengths or for
// a's length to be undetermined), or it's also ok to bind a single float to
// a non-array triple. All triples are considered equivalent for this test.
inline bool compatible_param (const TypeDesc& a, const TypeDesc& b)
{
    return equivalent (a, b) || (a.is_vec3() && b == TypeDesc::FLOAT);
}



void
ShaderInstance::parameters (const ParamValueList &params)
{
    // Seed the params with the master's defaults
    m_iparams = m_master->m_idefaults;
    m_fparams = m_master->m_fdefaults;
    m_sparams = m_master->m_sdefaults;

    m_instoverrides.resize (std::max (0, lastparam()));

    // Set the initial lockgeom and dataoffset on the instoverrides, based
    // on the master.
    for (int i = 0, e = (int)m_instoverrides.size(); i < e; ++i) {
        Symbol *sym = master()->symbol(i);
        m_instoverrides[i].lockgeom (sym->lockgeom());
        m_instoverrides[i].dataoffset (sym->dataoffset());
    }

    for (auto&& p : params) {
        if (p.name().size() == 0)
            continue;   // skip empty names
        int i = findparam (p.name());
        if (i >= 0) {
            // if (shadingsys().debug())
            //     shadingsys().info (" PARAMETER %s %s", p.name(), p.type());
            const Symbol *sm = master()->symbol(i);    // This sym in the master
            SymOverrideInfo *so = &m_instoverrides[i]; // Slot for sym's override info
            TypeSpec sm_typespec = sm->typespec(); // Type of the master's param
            if (sm_typespec.is_closure_based()) {
                // Can't assign a closure instance value.
                shadingsys().warningfmt("skipping assignment of closure: {}", sm->name());
                continue;
            }
            if (sm_typespec.is_structure())
                continue;    // structs are just placeholders; skip

            const void *data = p.data();
            float tmpdata[3]; // used for inline conversions to float/float[3]

            // Check type of parameter and matching symbol. Note that the
            // compatible accounts for indefinite-length arrays.
            TypeDesc paramtype = sm_typespec.simpletype();  // what the shader writer wants
            TypeDesc valuetype = p.type();                  // what the data provided actually is

            if (master()->shadingsys().relaxed_param_typecheck()) {
                // first handle cases where we actually need to modify the data (like setting a float parameter with an int)
                 if ((paramtype == TypeDesc::FLOAT || paramtype.is_vec3()) && valuetype.basetype == TypeDesc::INT && valuetype.basevalues() == 1) {
                    int val = *static_cast<const int*>(p.data());
                    float conv = float(val);
                    if (val != int(conv))
                        shadingsys().errorfmt("attempting to set parameter from wrong type would change the value: {} (set {:.9g} from {})",
                                              sm->name(), conv, val);
                    tmpdata[0] = conv;
                    data = tmpdata;
                    valuetype = TypeDesc::FLOAT;
                }

                // Relaxed rules just look to see that the types are isomorphic to each other (ie: same number of base values)
                // Note that:
                //   * basetypes must match exactly (int vs float vs string)
                //   * valuetype cannot be unsized (we must know the concrete number of values)
                //   * if paramtype is sized (or not an array) just check for the total number of entries
                //   * if paramtype is unsized (shader writer is flexible about how many values come in) -- make sure we are a multiple of the target type
                //   * allow a single float setting a vec3 (or equivalent)
                if (!( valuetype.basetype == paramtype.basetype &&
                      !valuetype.is_unsized_array() &&
                      ((!paramtype.is_unsized_array() && valuetype.basevalues() == paramtype.basevalues()) ||
                       ( paramtype.is_unsized_array() && valuetype.basevalues() % paramtype.aggregate == 0) ||
                       ( paramtype.is_vec3()          && valuetype == TypeDesc::FLOAT) ) )) {
                    // We are being very relaxed in this mode, so if the user _still_ got it wrong
                    // something more serious is at play and we should treat it as an error.
                    shadingsys().errorfmt("attempting to set parameter from incompatible type: {} (expected '{}', received '{}')",
                                          sm->name(), paramtype, valuetype);
                    continue;
                }
            } else if (!compatible_param(paramtype, valuetype)) {
                shadingsys().warningfmt("attempting to set parameter with wrong type: {} (expected '{}', received '{}')",
                                        sm->name(), paramtype, valuetype);
                continue;
            }

            // Mark that the override as an instance value
            so->valuesource (Symbol::InstanceVal);

            // Lock the param against geometric primitive overrides if the
            // master thinks it was so locked, AND the Parameter() call
            // didn't specify lockgeom=false (which would be indicated by
            // the parameter's interpolation being non-CONSTANT).
            bool lockgeom = (sm->lockgeom() &&
                             p.interp() == ParamValue::INTERP_CONSTANT);
            so->lockgeom (lockgeom);

            OSL_DASSERT(so->dataoffset() == sm->dataoffset());
            so->dataoffset (sm->dataoffset());

            if (paramtype.is_vec3() && valuetype == TypeDesc::FLOAT) {
                // Handle the special case of assigning a float for a triple
                // by replicating it into local memory.
                tmpdata[0] = *(const float *)data;
                tmpdata[1] = *(const float *)data;
                tmpdata[2] = *(const float *)data;
                data = &tmpdata;
                valuetype = paramtype;
            }

            if (paramtype.arraylen < 0) {
                // An array of definite size was supplied to a parameter
                // that was an array of indefinite size. Magic! The trick
                // here is that we need to allocate paramter space at the
                // END of the ordinary param storage, since when we assigned
                // data offsets to each parameter, we didn't know the length
                // needed to allocate this param in its proper spot.
                int nelements = valuetype.basevalues();
                // Store the actual length in the shader instance parameter
                // override info. Compute the length this way to account for relaxed
                // parameter checking (for example passing an array of floats to an array of colors)
                so->arraylen (nelements / paramtype.aggregate);
                // Allocate space for the new param size at the end of its
                // usual parameter area, and set the new dataoffset to that
                // position.
                if (paramtype.basetype == TypeDesc::FLOAT) {
                    so->dataoffset((int) m_fparams.size());
                    expand (m_fparams, nelements);
                } else if (paramtype.basetype == TypeDesc::INT) {
                    so->dataoffset((int) m_iparams.size());
                    expand (m_iparams, nelements);
                } else if (paramtype.basetype == TypeDesc::STRING) {
                    so->dataoffset((int) m_sparams.size());
                    expand (m_sparams, nelements);
                } else {
                    OSL_DASSERT (0 && "unexpected type");
                }
                // FIXME: There's a tricky case that we overlook here, where
                // an indefinite-length-array parameter is given DIFFERENT
                // definite length in subsequent rerenders. Don't do that.
            }
            else {
                // If the instance value is the same as the master's default,
                // just skip the parameter, let it "keep" the default.
                // Note that this can't/shouldn't happen for the indefinite-
                // sized array case, which is why we have it in the 'else'
                // clause of that test.
                void *defaultdata = m_master->param_default_storage(i);
                if (lockgeom &&
                      memcmp (defaultdata, data, valuetype.size()) == 0) {
                    // Must reset valuesource to default, in case the parameter
                    // was set already, and now is being changed back to default.
                    so->valuesource (Symbol::DefaultVal);
                }
            }

            // Copy the supplied data into place.
            memcpy (param_storage(i), data, valuetype.size());
        }
        else {
            shadingsys().warningfmt("attempting to set nonexistent parameter: {}", p.name());
        }
    }

    {
        // Adjust the stats
        ShadingSystemImpl &ss (shadingsys());
        size_t symmem = vectorbytes(m_instoverrides);
        size_t parammem = (vectorbytes(m_iparams) + vectorbytes(m_fparams) +
                           vectorbytes(m_sparams));
        spin_lock lock (ss.m_stat_mutex);
        ss.m_stat_mem_inst_syms += symmem;
        ss.m_stat_mem_inst_paramvals += parammem;
        ss.m_stat_mem_inst += (symmem+parammem);
        ss.m_stat_memory += (symmem+parammem);
    }
}



void
ShaderInstance::make_symbol_room (size_t moresyms)
{
    size_t oldsize = m_instsymbols.capacity();
    if (oldsize < m_instsymbols.size()+moresyms) {
        // Allocate a bit more than we need, so that most times we don't
        // need to reallocate.  But don't be wasteful by doubling or
        // anything like that, since we only expect a few to be added.
        const size_t extra_room = 10;
        size_t newsize = m_instsymbols.size() + moresyms + extra_room;
        m_instsymbols.reserve (newsize);

        // adjust stats
        spin_lock lock (shadingsys().m_stat_mutex);
        size_t mem = (newsize-oldsize) * sizeof(Symbol);
        shadingsys().m_stat_mem_inst_syms += mem;
        shadingsys().m_stat_mem_inst += mem;
        shadingsys().m_stat_memory += mem;
    }
}



void
ShaderInstance::add_connection (int srclayer, const ConnectedParam &srccon,
                                const ConnectedParam &dstcon)
{
    // specialize symbol in case of dstcon is an unsized array
    if (dstcon.type.is_unsized_array()) {
        SymOverrideInfo *so = &m_instoverrides[dstcon.param];
        so->arraylen(srccon.type.arraylength());

        const TypeDesc& type = srccon.type.simpletype();
        // Skip structs for now, they're just placeholders
        /*if      (t.is_structure()) {
        }
        else*/ if (type.basetype == TypeDesc::FLOAT) {
            so->dataoffset((int) m_fparams.size());
            expand (m_fparams,type.size());
        } else if (type.basetype == TypeDesc::INT) {
            so->dataoffset((int) m_iparams.size());
            expand (m_iparams, type.size());
        } else if (type.basetype == TypeDesc::STRING) {
            so->dataoffset((int) m_sparams.size());
            expand (m_sparams, type.size());
        }/* else if (t.is_closure()) {
            // Closures are pointers, so we allocate a string default taking
            // adventage of their default being NULL as well.
            so->dataoffset((int) m_sparams.size());
            expand (m_sparams, type.size());
        }*/ else {
            OSL_DASSERT (0 && "unexpected type");
        }
    }

    off_t oldmem = vectorbytes(m_connections);
    m_connections.emplace_back(srclayer, srccon, dstcon);

    // adjust stats
    off_t mem = vectorbytes(m_connections) - oldmem;
    {
        spin_lock lock (shadingsys().m_stat_mutex);
        shadingsys().m_stat_mem_inst_connections += mem;
        shadingsys().m_stat_mem_inst += mem;
        shadingsys().m_stat_memory += mem;
    }
}



void
ShaderInstance::evaluate_writes_globals_and_userdata_params ()
{
    writes_globals (false);
    userdata_params (false);
    for (auto&& s : symbols()) {
        if (s.symtype() == SymTypeGlobal && s.everwritten())
            writes_globals (true);
        if ((s.symtype() == SymTypeParam || s.symtype() == SymTypeOutputParam)
            && ! s.lockgeom() && ! s.connected())
            userdata_params (true);
        if (s.symtype() == SymTypeTemp) // Once we hit a temp, we'll never
            break;                      // see another global or param.
    }

    // In case this method is called before the Symbol vector is copied
    // (i.e. before copy_code_from_master is called), try to set
    // userdata_params as accurately as we can based on what we know from
    // the symbol overrides. This is very important to get instance merging
    // working correctly.
    int p = 0;
    for (auto&& s : m_instoverrides) {
        if (! s.lockgeom())
            userdata_params (true);
        ++p;
    }
}



void
ShaderInstance::copy_code_from_master (ShaderGroup &group)
{
    OSL_ASSERT (m_instops.empty() && m_instargs.empty());
    // reserve with enough room for a few insertions
    m_instops.reserve (master()->m_ops.size()+10);
    m_instargs.reserve (master()->m_args.size()+10);
    m_instops = master()->m_ops;
    m_instargs = master()->m_args;

    // Copy the symbols from the master
    OSL_ASSERT (m_instsymbols.size() == 0 &&
                "should not have copied m_instsymbols yet");
    m_instsymbols = m_master->m_symbols;

    // Copy the instance override data
    // Also set the renderer_output flags where needed.
    OSL_ASSERT (m_instoverrides.size() == (size_t)std::max(0,lastparam()));
    OSL_ASSERT (m_instsymbols.size() >= (size_t)std::max(0,lastparam()));
    if (m_instoverrides.size()) {
        for (size_t i = 0, e = lastparam();  i < e;  ++i) {
            Symbol *si = &m_instsymbols[i];
            if (m_instoverrides[i].valuesource() == Symbol::DefaultVal) {
                // Fix the length of any default-value variable length array
                // parameters.
                if (si->typespec().is_unsized_array())
                    si->arraylen (si->initializers());
            } else {
                if (m_instoverrides[i].arraylen())
                    si->arraylen (m_instoverrides[i].arraylen());
                si->valuesource (m_instoverrides[i].valuesource());
                si->connected_down (m_instoverrides[i].connected_down());
                si->lockgeom (m_instoverrides[i].lockgeom());
                si->dataoffset (m_instoverrides[i].dataoffset());
                si->set_dataptr(SymArena::Absolute, param_storage(i));
            }
            if (shadingsys().is_renderer_output (layername(), si->name(), &group)) {
                si->renderer_output (true);
                renderer_outputs (true);
            }
        }
    }
    evaluate_writes_globals_and_userdata_params ();
    off_t symmem = vectorbytes(m_instsymbols) - vectorbytes(m_instoverrides);
    SymOverrideInfoVec().swap (m_instoverrides);  // free it

    // adjust stats
    {
        spin_lock lock (shadingsys().m_stat_mutex);
        shadingsys().m_stat_mem_inst_syms += symmem;
        shadingsys().m_stat_mem_inst += symmem;
        shadingsys().m_stat_memory += symmem;
    }
}



std::string
ConnectedParam::str (const ShaderInstance *inst, bool unmangle) const
{
    const Symbol *s = inst->symbol(param);
    return Strutil::sprintf ("%s%s%s (%s)",
                            unmangle ? s->unmangled() : string_view(s->name()),
                            arrayindex >= 0 ? Strutil::sprintf("[%d]", arrayindex) : std::string(),
                            channel >= 0 ? Strutil::sprintf("[%d]", channel) : std::string(),
                            type);
}



std::string
Connection::str (const ShaderGroup &group, const ShaderInstance *dstinst,
                 bool unmangle) const
{
    return Strutil::sprintf ("%s -> %s", src.str(group[srclayer], unmangle),
                             dst.str(dstinst, unmangle));
}



// Are the two vectors equivalent(a[i],b[i]) in each of their members?
template<class T>
inline bool
equivalent (const std::vector<T> &a, const std::vector<T> &b)
{
    if (a.size() != b.size())
        return false;
    typename std::vector<T>::const_iterator ai, ae, bi;
    for (ai = a.begin(), ae = a.end(), bi = b.begin();  ai != ae;  ++ai, ++bi)
        if (! equivalent(*ai, *bi))
            return false;
    return true;
}



/// Are two symbols equivalent (from the point of view of merging
/// shader instances)?  Note that this is not a true ==, it ignores
/// the m_data, m_node, and m_alias pointers!
static bool
equivalent (const Symbol &a, const Symbol &b)
{
    // If they aren't used, don't consider them a mismatch
    if (! a.everused() && ! b.everused())
        return true;

    // Different symbol types or data types are a mismatch
    if (a.symtype() != b.symtype() || a.typespec() != b.typespec())
        return false;

    // Don't consider different names to be a mismatch if the symbol
    // is a temp or constant.
    if (a.symtype() != SymTypeTemp && a.symtype() != SymTypeConst &&
        a.name() != b.name())
        return false;
    // But constants had better match their values!
    if (a.symtype() == SymTypeConst &&
        memcmp (a.data(), b.data(), a.typespec().simpletype().size()))
        return false;

    return a.has_derivs() == b.has_derivs() &&
        a.lockgeom() == b.lockgeom() &&
        a.valuesource() == b.valuesource() &&
        a.fieldid() == b.fieldid() &&
        a.initbegin() == b.initbegin() &&
        a.initend() == b.initend()
        ;
}



bool
ShaderInstance::mergeable (const ShaderInstance &b, const ShaderGroup& /*g*/) const
{
    // Must both be instances of the same master -- very fast early-out
    // for most potential pair comparisons.
    if (master() != b.master())
        return false;

    // If one or both instances are directly hooked up to renderer outputs,
    // don't merge them.
    if (renderer_outputs() || b.renderer_outputs())
        return false;

    // If the shaders haven't been optimized yet, they don't yet have
    // their own symbol tables and instructions (they just refer to
    // their unoptimized master), but they may have an "instance
    // override" vector that describes which parameters have
    // instance-specific values or connections.
    bool optimized = (m_instsymbols.size() != 0 || m_instops.size() != 0);

    // Same instance overrides
    if (m_instoverrides.size() || b.m_instoverrides.size()) {
        OSL_ASSERT (! optimized);  // should not be post-opt
        OSL_ASSERT (m_instoverrides.size() == b.m_instoverrides.size());
        for (size_t i = 0, e = m_instoverrides.size();  i < e;  ++i) {
            if ((m_instoverrides[i].valuesource() == Symbol::DefaultVal ||
                 m_instoverrides[i].valuesource() == Symbol::InstanceVal) &&
                (b.m_instoverrides[i].valuesource() == Symbol::DefaultVal ||
                 b.m_instoverrides[i].valuesource() == Symbol::InstanceVal)) {
                // If both params are defaults or instances, let the
                // instance parameter value checking below handle
                // things. No need to reject default-vs-instance
                // mismatches if the actual values turn out to be the
                // same later.
                continue;
            }

            if (! (equivalent(m_instoverrides[i], b.m_instoverrides[i]))) {
                const Symbol *sym = mastersymbol(i);  // remember, it's pre-opt
                const Symbol *bsym = b.mastersymbol(i);
                if (! sym->everused_in_group() && ! bsym->everused_in_group())
                    continue;
                return false;
            }
            // But still, if they differ in their lockgeom'edness, we can't
            // merge the instances.
            if (m_instoverrides[i].lockgeom() != b.m_instoverrides[i].lockgeom()) {
                return false;
            }
        }
    }

    // Make sure that the two nodes have the same parameter values.  If
    // the group has already been optimized, it's got an
    // instance-specific symbol table to check; but if it hasn't been
    // optimized, we check the symbol table in the master.
    for (int i = firstparam();  i < lastparam();  ++i) {
        const Symbol *sym = optimized ? symbol(i) : mastersymbol(i);
        if (! sym->everused_in_group())
            continue;
        if (sym->typespec().is_closure())
            continue;   // Closures can't have instance override values
        if ((sym->valuesource() == Symbol::InstanceVal || sym->valuesource() == Symbol::DefaultVal)
            && memcmp (param_storage(i), b.param_storage(i),
                       sym->typespec().simpletype().size())) {
            return false;
        }
    }

    if (run_lazily() != b.run_lazily()) {
        return false;
    }

    // The connection list need to be the same for the two shaders.
    if (m_connections.size() != b.m_connections.size()) {
        return false;
    }
    if (m_connections != b.m_connections) {
        return false;
    }

    // Make sure system didn't ask for instances that query userdata to be
    // immune from instance merging.
    if (! shadingsys().m_opt_merge_instances_with_userdata
        && (userdata_params() || b.userdata_params())) {
        return false;
    }

    // If there are no "local" ops or symbols, this instance hasn't been
    // optimized yet.  In that case, we've already done enough checking,
    // since the masters being the same and having the same instance
    // params and connections is all it takes.  The rest (below) only
    // comes into play after instances are more fully elaborated from
    // their masters in order to be optimized.
    if (!optimized) {
        return true;
    }

    // Same symbol table
    if (! equivalent (m_instsymbols, b.m_instsymbols)) {
        return false;
    }

    // Same opcodes to run
    if (! equivalent (m_instops, b.m_instops)) {
        return false;
    }
    // Same arguments to the ops
    if (m_instargs != b.m_instargs) {
        return false;
    }

    // Parameter and code ranges
    if (m_firstparam != b.m_firstparam ||
        m_lastparam != b.m_lastparam ||
        m_maincodebegin != b.m_maincodebegin ||
        m_maincodeend != b.m_maincodeend ||
        m_Psym != b.m_Psym || m_Nsym != b.m_Nsym) {
        return false;
    }

    // Nothing left to check, they must be identical!
    return true;
}


}; // namespace pvt



ShaderGroup::ShaderGroup (string_view name)
{
    m_id = ++(*(atomic_int *)&next_id);
    if (name.size()) {
        m_name = name;
    } else {
        // No name -- make one up using the unique
        m_name = ustring::sprintf ("unnamed_group_%d", m_id);
    }
}



ShaderGroup::ShaderGroup (const ShaderGroup &g, string_view name)
    : ShaderGroup(name)  // delegate most of the work
{
    m_num_entry_layers = g.m_num_entry_layers;
    m_layers = g.m_layers;
}



ShaderGroup::~ShaderGroup ()
{
#if 0
    if (m_layers.size()) {
        ustring name = m_layers.back()->layername();
        std::cerr << "Shader group " << this 
                  << " id #" << m_layers.back()->id() << " (" 
                  << (name.c_str() ? name.c_str() : "<unnamed>")
                  << ") executed on " << executions() << " points\n";
    } else {
        std::cerr << "Shader group " << this << " (no layers?) " 
                  << "executed on " << executions() << " points\n";
    }
#endif
}



int
ShaderGroup::find_layer (ustring layername) const
{
    int i;
    for (i = nlayers()-1; i >= 0 && layer(i)->layername() != layername; --i)
        ;
    return i;  // will be -1 if we never found a match
}



const Symbol *
ShaderGroup::find_symbol (ustring layername, ustring symbolname) const
{
    for (int layer = nlayers()-1;  layer >= 0;  --layer) {
        const ShaderInstance *inst (m_layers[layer].get());
        if (layername.size() && layername != inst->layername())
            continue;  // They asked for a specific layer and this isn't it
        int symidx = inst->findsymbol (symbolname);
        if (symidx >= 0)
            return inst->symbol (symidx);
    }
    return NULL;
}



void
ShaderGroup::clear_entry_layers ()
{
    for (int i = 0;  i < nlayers();  ++i)
        m_layers[i]->entry_layer (false);
    m_num_entry_layers = 0;
}



void
ShaderGroup::mark_entry_layer (int layer)
{
    if (layer >= 0 && layer < nlayers() && ! m_layers[layer]->entry_layer()) {
        m_layers[layer]->entry_layer(true);
        ++m_num_entry_layers;
    }
}



std::string
ShaderGroup::serialize () const
{
    std::ostringstream out;
    out.imbue (std::locale::classic());  // force C locale
    out.precision (9);
    lock_guard lock (m_mutex);
    for (int i = 0, nl = nlayers(); i < nl; ++i) {
        const ShaderInstance *inst = m_layers[i].get();

        bool dstsyms_exist = inst->symbols().size();
        for (int p = 0;  p < inst->lastparam(); ++p) {
            const Symbol *s = dstsyms_exist ? inst->symbol(p) : inst->mastersymbol(p);
            OSL_ASSERT (s);
            if (!s || (s->symtype() != SymTypeParam && s->symtype() != SymTypeOutputParam))
                continue;
            Symbol::ValueSource vs = dstsyms_exist ? s->valuesource()
                                                   : inst->instoverride(p)->valuesource();
            if (vs == Symbol::InstanceVal) {
                TypeDesc type = s->typespec().simpletype();
                int offset = s->dataoffset();
                if (type.is_unsized_array() && ! dstsyms_exist) {
                    // If we're being asked to serialize a group that isn't
                    // yet optimized, any "unsized" arrays will have their
                    // concrete length and offset in the SymOverrideInfo,
                    // not in the Symbol belonging to the instance.
                    type.arraylen = inst->instoverride(p)->arraylen();
                    offset = inst->instoverride(p)->dataoffset();
                }
                out << "param " << type << ' ' << s->name();
                int nvals = type.numelements() * type.aggregate;
                if (type.basetype == TypeDesc::INT) {
                    const int *vals = &inst->m_iparams[offset];
                    for (int i = 0; i < nvals; ++i)
                        out << ' ' << vals[i];
                } else if (type.basetype == TypeDesc::FLOAT) {
                    const float *vals = &inst->m_fparams[offset];
                    for (int i = 0; i < nvals; ++i)
                        out << ' ' << vals[i];
                } else if (type.basetype == TypeDesc::STRING) {
                    const ustring *vals = &inst->m_sparams[offset];
                    for (int i = 0; i < nvals; ++i)
                        out << ' ' << '\"' << Strutil::escape_chars(vals[i]) << '\"';
                } else {
                    OSL_ASSERT_MSG (0, "unknown type for serialization: %s (%s)",
                                    type.c_str(), s->typespec().c_str());
                }
                bool lockgeom = dstsyms_exist ? s->lockgeom()
                                              : inst->instoverride(p)->lockgeom();
                if (! lockgeom)
                    out << Strutil::sprintf (" [[int lockgeom=%d]]", lockgeom);
                out << " ;\n";
            }
        }
        out << "shader " << inst->shadername() << ' ' << inst->layername() << " ;\n";
        for (int c = 0, nc = inst->nconnections(); c < nc; ++c) {
            const Connection &con (inst->connection(c));
            OSL_ASSERT (con.srclayer >= 0);
            const ShaderInstance *srclayer = m_layers[con.srclayer].get();
            OSL_ASSERT (srclayer);
            ustring srclayername = srclayer->layername();
            OSL_ASSERT (con.src.param >= 0 && con.dst.param >= 0);
            bool srcsyms_exist = srclayer->symbols().size();
            ustring srcparam = srcsyms_exist ? srclayer->symbol(con.src.param)->name()
                                             : srclayer->mastersymbol(con.src.param)->name();
            ustring dstparam = dstsyms_exist ? inst->symbol(con.dst.param)->name()
                                             : inst->mastersymbol(con.dst.param)->name();
            // FIXME: Assertions to be sure we don't yet support individual
            // channel or array element connections. Fix eventually.
            OSL_ASSERT (con.src.arrayindex == -1 && con.src.channel == -1);
            OSL_ASSERT (con.dst.arrayindex == -1 && con.dst.channel == -1);
            out << "connect " <<  srclayername << '.' << srcparam << ' '
                << inst->layername() << '.' << dstparam << " ;\n";
        }
    }
    return out.str();
}


OSL_NAMESPACE_EXIT
